﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using Twinkle.Tweak;
using Twinkle.Tweak.CredentialProviders;
using Twinkle.Data;
using Krile.Kernel;

namespace Twinkle.Common
{
    /// <summary>
    /// IAPIStreaming の UserStreams 実装
    /// </summary>
    public class UserStreams
    {
        string[] query = null;
        StreamingController streaming = null;

        public IEnumerable<TChirpElement> GetTimelineStream(Account descriptor)
        {
            if (streaming != null)
            {
                if (!streaming.Disposed)
                    streaming.Dispose();
                streaming = null;
            }
            // クエリ組立
            string track = null;
            if (query != null)
            {
                track = String.Join(",", query);
            }

            if (Connect(descriptor, track))
            {
                // 接続成功
                return streaming.EnumerateStreaming();
            }
            else
            {
                // 失敗
                return null;
            }
        }

        /// <summary>
        /// ストリーミングAPIへ接続します。<para />
        /// 接続に失敗した場合、規定アルゴリズムに沿って自動で再接続を試みます。<para />
        /// それでも失敗する場合はfalseを返します。この場合、再接続を試みてはいけません。
        /// </summary>
        /// <param name="track">トラック対象キーワード</param>
        private bool Connect(Account descriptor, string track)
        {
            try
            {
                int failureCounter = 0;
                while (true)
                {
                    // 接続
                    try
                    {
                        streaming = StreamingController.BeginStreaming(
                            (OAuth)descriptor.CredentialProvider, StreamingController.StreamingType.user,
                            track: track);
                        Bridge.MainForm.SetStatusText("@" + descriptor.UserId + ": User Streamsへ接続しました。", 5);
                    }
                    catch (WebException we)
                    {
                        if ((int)we.Status == 420)
                        {
                            // 多重接続されている
                            Bridge.MainForm.SetStatusText("@" + descriptor.UserId + ": 多重接続を検知しました。User Streamsに接続できません。");
                            return false;
                        }
                        if (we.Status != WebExceptionStatus.Timeout) // タイムアウト例外なら再試行
                            throw we;
                        else
                            Bridge.MainForm.SetStatusText("@" + descriptor.UserId + ": " + we.ToString());
                    }
                    if (streaming != null)
                        return true;

                    if (failureCounter > 0)
                    {
                        if (failureCounter > 300)
                        {
                            Bridge.MainForm.SetStatusText("@" + descriptor.UserId + ": User Streamsに接続できませんでした。");
                        }
                        else
                        {
                            Bridge.MainForm.SetStatusText("@" + descriptor.UserId + ": User Streamsへの接続に失敗。再試行まで" + (failureCounter / 1000).ToString() + "秒待機...");

                            // ウェイトカウント
                            Thread.Sleep(failureCounter);
                            Bridge.MainForm.SetStatusText("@" + descriptor.UserId + ": User Streamsへの接続を再試行します...");
                        }
                    }
                    else
                    {
                        Bridge.MainForm.SetStatusText("@" + descriptor.UserId + ": User Streamsへの接続に失敗しました。再試行します...");
                    }

                    // 最初に失敗したらすぐに再接続
                    // ２回目以降はその倍に増やしていく
                    // 300を超えたら接続失敗で戻る
                    if (failureCounter == 0)
                    {
                        failureCounter = 30;
                    }
                    else
                    {
                        failureCounter *= 2;
                    }
                }
            }
            catch (Exception e)
            {
                Bridge.MainForm.SetStatusText("@" + descriptor.UserId + ":  User Streamsに接続できません:" + e.Message, 5);
                return false;
            }
        }

        public void EndStreaming()
        {
            if (streaming != null)
                streaming.EndStreaming();
            streaming = null;
        }

    }
}
